package com.kamistoat.meimeimall.common.utils;

import com.kamistoat.meimeimall.common.constant.AuthConstant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * Jwt工具类。
 * Jwt是SpringSecurity中的概念，是一种JSON风格的轻量级授权和身份认证规范。可实现无状态分布式的Web应用授权。
 * Jwt相比与完整的用户令牌，更加轻量化。一般来说我们都是使用用户令牌的几个唯一字段来构建Jwt。
 * <p>
 * 在本项目中，用户令牌(LoginUser类)包含非常多的内容，且很多内容只有后端需要，前端并不需要，因此完整的用户令牌会缓存在后端。
 * 然后从用户令牌中提取出 token、userId、userName，仅使用这三个字段来构建Jwt，然后将Jwt传递给前端。
 * 前端每次发送请求时会在请求头中携带Jwt (在请求头中以 Authorization 为 key， 以 Jwt数据为 value)。
 * 显然，Jwt数据也是通过token唯一标识的，且和对应的完整用户令牌的token相同，因此后端可以根据前端中jwt数据的token来寻找对应的完整用户令牌进行校验。
 * <p>
 * 需要引入 jjwt 依赖
 */
public class JwtUtils {
    public static String secret = AuthConstant.JWT_SECRET;

    /**
     * 根据k-v数据生成 Jwt。
     * 该方法和输入的k-v中有那些字段毫无关系。只是本项目中固定使用 用户令牌的 token、userId、userName 来构建Jwt，
     * 因此输入 claims中应该只含有 token/userId/userName 三个字段的k-v对
     *
     * @param claims Map<>实现类，将根据内容制作Jwt。本项目中应只含有 token/userId/userName 三个字段的k-v对
     * @return 构建好的 jwt String
     */
    public static String createJwtString(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 从jwt中解构出k-v格式的数据，制作成Map。
     *
     * @param jwtString Jwt数据
     * @return 解构出的k-v数据对。Claims其实就是一个Map类
     */
    public static Claims parseJwtString(String jwtString) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(jwtString).getBody();
    }

    /**
     * 从jwt中解构出k-v格式的数据，制作成Map。
     * 然后从Map中提取出 userKey(其实就是 jwt和用户令牌的唯一标识token)
     *
     * @param jwtString jwt数据
     * @return userKey(token)
     */
    public static String getUserTokenFromJwtString(String jwtString) {
        Claims claims = parseJwtString(jwtString);
        return getValueFromJwtClaim(claims, AuthConstant.JWT_USER_TOKEN);
    }

    /**
     * 直接在 k-v格式的Map中获取userKey
     *
     * @param claims k-v格式的 Map数据
     * @return 用户ID
     */
    public static String getUserTokenFromJwtClaim(Claims claims) {
        return getValueFromJwtClaim(claims, AuthConstant.JWT_USER_TOKEN);
    }

    /**
     * 从jwt中解构出k-v格式的数据，制作成Map。
     * 然后从Map中提取出 userId
     *
     * @param jwtString jwt数据
     * @return userId
     */
    public static String getUserIdFromJwtString(String jwtString) {
        Claims claims = parseJwtString(jwtString);
        return getValueFromJwtClaim(claims, AuthConstant.JWT_USER_ID);
    }

    /**
     * 直接在 k-v格式的Map中获取userId
     *
     * @param claims k-v格式的 Map数据
     * @return userId
     */
    public static String getUserIdFromJwtClaim(Claims claims) {
        return getValueFromJwtClaim(claims, AuthConstant.JWT_USER_ID);
    }

    /**
     * 从jwt中解构出k-v格式的数据，制作成Map。
     * 然后从Map中提取出 userName
     *
     * @param jwtString jwt数据
     * @return userName
     */
    public static String getUserNameFromJwtString(String jwtString) {
        Claims claims = parseJwtString(jwtString);
        return getValueFromJwtClaim(claims, AuthConstant.JWT_USER_NAME);
    }

    /**
     * 直接在 k-v格式的Map中获取userName
     *
     * @param claims k-v格式的 Map数据
     * @return userName
     */
    public static String getUserNameFromJwtClaim(Claims claims) {
        return getValueFromJwtClaim(claims, AuthConstant.JWT_USER_NAME);
    }

    /**
     * 从k-v形式的 Claims中获取对应key的value
     *
     * @param claims 实际上就是一个 Map<> 的实现类
     * @param key    键
     * @return 值
     */
    public static String getValueFromJwtClaim(Claims claims, String key) {
        return String.valueOf(claims.get(key));
    }

    /**
     * 获取当前Request请求中的Jwt数据. Jwt字符串中含有完整用户令牌的token/userId/userName。
     */
    public static String getJwtStringServlet() {
        HttpServletRequest request = getRequest();
        // 传入当前的 Request
        return request == null ? "" : getJwtStringServlet(request);
    }

    /**
     * 获取指定Request请求中的Jwt数据. Jwt是简易用户令牌，含有完整用户令牌的token/userId/userName
     */
    public static String getJwtStringServlet(HttpServletRequest httpServletRequest) {
        // 从输入 Request的请求头中获取 key为 "Authorization" 的简易用户令牌信息
        String jwtToken = httpServletRequest.getHeader(AuthConstant.JWT_AUTHENTICATION);
        return replaceTokenPrefix(jwtToken);
    }

    /**
     * 裁剪JwtString前缀.
     * 就是和前端商量好，要求JwtString的提交格式为 `Bearer+空格+JwtString` ，在请求头中以 `Authorization` 进行提交.
     * 所以这里会将JwtString的前缀裁剪掉.
     *
     * @param jwtString 未经裁剪的 简易用户令牌
     * @return 裁剪后的 简易用户令牌
     */
    public static String replaceTokenPrefix(String jwtString) {
        if (StringUtils.isEmpty(jwtString) && jwtString.startsWith(AuthConstant.JWT_PREFIX)) {
            jwtString = jwtString.replaceFirst(AuthConstant.JWT_PREFIX, "");
        }
        return jwtString;
    }

    /**
     * 生成BCryptPasswordEncoder密码
     *
     * @param password 密码
     * @return 加密字符串
     */
    public static String encryptPassword(String password) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.encode(password);
    }

    /**
     * 判断密码是否相同
     *
     * @param rawPassword     一般是前端传递的未经加密的明文密码
     * @param encodedPassword 一般是从数据库中查询出的加密后的密码
     * @return 结果
     */
    public static boolean matchesPassword(String rawPassword, String encodedPassword) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }

    /**
     * 获取当前请求request实例。
     * 所有从request中提取请求参数的操作都需要在 HttpServletRequest上进行
     */
    public static HttpServletRequest getRequest() {
        try {
            return getRequestAttributes().getRequest();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取当前请求的 Attributes
     */
    public static ServletRequestAttributes getRequestAttributes() {
        try {
            // RequestContextHolder只能在当前线程中获取正确的 RequestAttributes，如果在子线程中尝试获取会获得一个空结果
            // 原因很简单，RequestContextHolder通过ThreadLocal来实现功能
            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
            return (ServletRequestAttributes) attributes;
        } catch (Exception e) {
            return null;
        }
    }
}
